/**
 * \file Cpu.cc
 * \brief Cpu facilities
 * \author Corey Tabaka
 */

/*
   Copyright 2006 Corey Tabaka

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

#include <Namespace.h>
#include <platform/Cpu.h>
#include <platform/processor.h>
#include <platform/msr.h>

#include <stdio.h>
#include <string.h>
 
/** Processor ticks per nanosecond. */
uint64 cpuTicksPerNano = 1;

/** Processor ticks per microsecond. */
uint64 cpuTicksPerMicro = 1;

/** Processor ticks per second. */
uint64 cpuTicksPerSec = 1;

static cpu_info_x86_t bspCpuInfo;

static void getCpuVendor(cpu_info_x86_t &c);
static bool getModelName(cpu_info_x86_t &c);

extern cpu_device_t *cpuDevices[X86_VENDOR_NUM];

static Var cpuVendor("hw.cpu.vendor", "Unknown", VF_SYSTEM);
static Var cpuModel("hw.cpu.model", "Unknown", VF_SYSTEM);

static Var hasMMX("hw.cpu.mmx", 0LL, VF_SYSTEM);
static Var hasSSE("hw.cpu.sse", 0LL, VF_SYSTEM);
static Var hasSSE2("hw.cpu.sse2", 0LL, VF_SYSTEM);
static Var hasSSE3("hw.cpu.sse3", 0LL, VF_SYSTEM);
static Var hasFXSR("hw.cpu.fxsr", 0LL, VF_SYSTEM);
static Var hasPSE("hw.cpu.pse", 0LL, VF_SYSTEM);
 
void Cpu::initialize(void) {
	// TODO: first detect support for CPUID
	
	// get vendor name of BSP
	cpuid(0x00000000, &bspCpuInfo.cpuidLevel,
		(int *)&bspCpuInfo.vendorId[0],
		(int *)&bspCpuInfo.vendorId[8],
		(int *)&bspCpuInfo.vendorId[4]);
	
	cpuVendor = bspCpuInfo.vendorId;
	
	// TODO: compare vendorId to manufacturer IDs
	getCpuVendor(bspCpuInfo);
	
	// detect CPU family, model, cache align, etc...
	bspCpuInfo.cacheAlignment = 32;
	bspCpuInfo.family = 4;
	
	if (bspCpuInfo.cpuidLevel >= 0x00000001) {
		int eax, ebx, ecx, edx;
		cpuid(0x00000001, &eax, &ebx, &ecx, &edx);
		
		// detect family/model
		bspCpuInfo.family = (eax >> 4) & 0x0f;
		bspCpuInfo.model = (eax >> 8) & 0x0f;
		
		if (bspCpuInfo.family = 0x0f) {
			bspCpuInfo.family += (eax >> 20) & 0xff;
			bspCpuInfo.model += ((eax >> 16) & 0x0f) << 4;
		}
		
		bspCpuInfo.mask = eax & 0x0f;
		
		// read cache alignment
		if (edx & (1<<19)) {
			bspCpuInfo.cacheAlignment = ((ebx >> 8) & 0xff) * 8;
		}
		
		// detect MMX/SSE/SSE2/SSE3 extensions
		hasMMX = (edx & (1<<23)) != 0;
		hasFXSR = (edx & (1<<24)) != 0;
		hasSSE = (edx & (1<<25)) != 0;
		hasSSE2 = (edx & (1<<26)) != 0;
		hasSSE3 = (ecx & 1) != 0;
		
		// other relevant extensions
		hasPSE = (edx & (1<<3)) != 0;
	}
	
	getModelName(bspCpuInfo);
	
	// start with the generic descriptions
	cpuVendor = bspCpuInfo.vendorId;
	cpuModel = bspCpuInfo.modelId;
	
	// see if there is a better description for the vendor and model
	cpu_device_t *cpudev = cpuDevices[bspCpuInfo.vendor];
	if (cpudev) {
		cpuVendor = cpudev->vendor;
		
		for (int i=0; i<4; i++) {
			if (cpudev->models[i].family == bspCpuInfo.family) {
				if (cpudev->models[i].modelNames[bspCpuInfo.model]) {
					cpuModel = cpudev->models[i].modelNames[bspCpuInfo.model];
				}
				
				break;
			}
		}
	}			
	
	printf("        %s\n", cpuModel.svalue);
	
	Namespace::registerVar(cpuVendor);
	Namespace::registerVar(cpuModel);
	Namespace::registerVar(hasMMX);
	Namespace::registerVar(hasSSE);
	Namespace::registerVar(hasSSE2);
	Namespace::registerVar(hasSSE3);
	Namespace::registerVar(hasFXSR);
	Namespace::registerVar(hasPSE);
}

// CPU info tables
static cpu_device_t intelCpuDevice = {
	"Intel",
	{ "GenuineIntel" },
	{
		{ X86_VENDOR_INTEL, 4,
			{ 
				"486 DX-25/33", 
				"486 DX-50", 
				"486 SX", 
				"486 DX/2", 
				"486 SL", 
				"486 SX/2", 
				"486 DX/2-WB", 
				"486 DX/4", 
				"486 DX/4-WB",
			}
		},
		{ X86_VENDOR_INTEL, 5,
			{ 
				"Pentium 60/66 A-step", 
				"Pentium 60/66", 
				"Pentium 75 - 200",
				"OverDrive PODP5V83", 
				"Pentium MMX",
				"Mobile Pentium 75 - 200", 
				"Mobile Pentium MMX",
			}
		},
		{ X86_VENDOR_INTEL, 6,
			{ 
			  "Pentium Pro A-step",
			  "Pentium Pro", 
			  "Pentium II (Klamath)", 
			  "Pentium II (Deschutes)", 
			  "Pentium II (Deschutes)", 
			  "Mobile Pentium II",
			  "Pentium III (Katmai)", 
			  "Pentium III (Coppermine)", 
			  "Pentium III (Cascades)",
			  "Pentium III (Tualatin)",
			}
		},
		{ X86_VENDOR_INTEL, 15,
			{
			  "Pentium 4 (Unknown)",
			  "Pentium 4 (Willamette)",
			  "Pentium 4 (Northwood)",
			  "Pentium 4 (Foster)",
			  "Pentium 4 (Foster)",
			}
		},
	},
};

static cpu_device_t amdCpuDevice = {
	"AMD",
	{ "AuthenticAMD" },
	{
		{ X86_VENDOR_AMD, 4,
			{
				0,
				0,
				0,
				"486 DX/2",
				0,
				0,
				0,
				"486 DX/2-WB",
				"486 DX/4", 
				"486 DX/4-WB", 
				0,
				0,
				0,
				0,
				"Am5x86-WT",
				"Am5x86-WB",
			}
		},
	},
};

cpu_device_t *cpuDevices[X86_VENDOR_NUM] = {
	&intelCpuDevice,
	0,
	&amdCpuDevice,
};

static void getCpuVendor(cpu_info_x86_t &c) {
	char *v = c.vendorId;
	for (int i=0; i < X86_VENDOR_NUM; i++) {
		if (cpuDevices[i]) {
			if (!strcmp(v, cpuDevices[i]->ident[0]) || (cpuDevices[i]->ident[1]
				&& !strcmp(v, cpuDevices[i]->ident[1]))) {
				c.vendor = i;
				break;
			}
		}
	}
}

static bool getModelName(cpu_info_x86_t &c) {
	int *v;
	char *p, *q;

	if (cpuid_eax(0x80000000) < 0x80000004) {
		return false;
	}

	v = (int *) c.modelId;
	cpuid(0x80000002, &v[0], &v[1], &v[2], &v[3]);
	cpuid(0x80000003, &v[4], &v[5], &v[6], &v[7]);
	cpuid(0x80000004, &v[8], &v[9], &v[10], &v[11]);
	c.modelId[48] = 0;

	// Intel chips right-justify this string for some dumb reason;
	// undo that brain damage
	p = q = &c.modelId[0];
	while (*p == ' ') {
		p++;
	}
	
	if (p != q) {
		while (*p) {
			*q++ = *p++;
		}
		
	     while (q <= &c.modelId[48]) {
			*q++ = '\0';	// zero-pad the rest
		}
	}

	return true;
}
